回调验签
在开发者中心「企业回调地址」保存后,平台会签发 AppId 与 Secret。之后每次推送(含页面上的「测试」)都会带 HMAC 签名。请在接收服务里验签通过后再处理业务。
info
AppId 只用来识别是哪份订阅,会出现在 JSON 和请求头里,不能当唯一放行条件。
Secret 只用于算签名,不要写进前端、不要打进日志。
请求约定
推送均为 HTTPS POST,Content-Type: application/json。
| 请求头 | 说明 |
|---|---|
X-Gausium-App-Id | 与开发者中心 AppId 一致 |
X-Gausium-Timestamp | 毫秒时间戳。与本地相差超过 5 分钟则拒绝 |
X-Gausium-Nonce | 随机串。建议缓存 10 分钟去重 |
X-Gausium-Signature | sha256=<hex>,HMAC-SHA256(Secret) |
签名字符串必须用原始 body 字节(不要先 parse 再序列化):
timestamp + "\n" + nonce + "\n" + rawBody
计算 HMAC-SHA256(Secret, 签名字符串),输出小写 hex,与去掉 sha256= 后的值做恒定时间比较。
测试包带 payload.test = true,验过返回 200 即可,不要当真实告警或任务。
校验步骤
- 只暴露
https://接收地址。 - 四个请求头缺一即拒绝。
X-Gausium-App-Id等于本订阅 AppId。|nowMs - timestamp| < 5 × 60 × 1000。- 按上一节算签名并恒定时间比较。
- 通过后再解析 JSON;失败返回 4xx,不要落库。
存量仅 appId + payload、未启用 Secret 的订阅仍只按 body 里的 AppId 识别;新签发订阅必须验签。
相关页面
示例
Python
import hmac, hashlib, time
def verify(headers, raw_body: bytes, app_id: str, secret: str) -> bool:
got_app = headers.get("X-Gausium-App-Id", "")
ts = headers.get("X-Gausium-Timestamp", "")
nonce = headers.get("X-Gausium-Nonce", "")
sig = headers.get("X-Gausium-Signature", "")
if got_app != app_id or not ts or not nonce or not sig.startswith("sha256="):
return False
if abs(int(time.time() * 1000) - int(ts)) > 5 * 60 * 1000:
return False
msg = f"{ts}\n{nonce}\n".encode() + raw_body
expect = "sha256=" + hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
return hmac.compare_digest(expect, sig)
Node.js
const crypto = require("crypto");
function verify(headers, rawBodyBuf, appId, secret) {
const gotApp = headers["x-gausium-app-id"];
const ts = headers["x-gausium-timestamp"];
const nonce = headers["x-gausium-nonce"];
const sig = headers["x-gausium-signature"] || "";
if (gotApp !== appId || !ts || !nonce || !sig.startsWith("sha256=")) return false;
if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) return false;
const msg = Buffer.concat([Buffer.from(`${ts}\n${nonce}\n`), rawBodyBuf]);
const expect = "sha256=" + crypto.createHmac("sha256", secret).update(msg).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expect), Buffer.from(sig));
}
Java
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((timestamp + "\n" + nonce + "\n").getBytes(StandardCharsets.UTF_8));
mac.update(rawBody);
String expect = "sha256=" + Hex.encodeHexString(mac.doFinal());
return MessageDigest.isEqual(
expect.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
连通性测试
开发者中心「测试」 会向页面填写的地址 POST 一次 payload.test = true 的探测包,只发一次,不修改配置。已有 Secret 时,探测请求带与正式推送完全相同的四个签名头;可用它先验证你的验签逻辑。
若对方返回非 2xx 或连不上,接口本身仍返回 code = 0,结果放在 data.httpCode 与 data.reachable 中,便于页面区分「地址不可达」与「参数不合法」。
相关错误码
| 错误码 | 说明 | 触发条件 |
|---|---|---|
| 20059 | 回调地址必填 | 测试时地址为空 |
| 20060 | 回调地址不合法 | 地址不是以 http:// 或 https:// 开头,或超过 150 个字符 |